require(tidyverse)
require(flowCore)
require(flowClust)
require(openCyto)
require(ggcyto)
require(ggridges)
require(cowplot)
old <- theme_set(theme_minimal())

Data

Plate design

See 20230208-sample-list.txt

Analysis

Import data and edit the meta data

import the data from the RDSS (just once) and then write it to the local disk

Simplify the sample names

# import the longest substring function from the PTXQC package (https://rdrr.io/cran/PTXQC/man/LCSn.html)
source("../../script/20220326-simplify-names-subroutine.R")
oriNames <- sampleNames(fs)
shortNames <- simplifyNames(oriNames) %>% 
  gsub(".fcs","",.)
sampleNames(fs) <- shortNames

Metadata

sample <- read.csv("20230208-sample-list.csv") %>% 
  column_to_rownames(var = "file")
pData(fs) <- sample

EDA

The code below demonstrates how to subset a flowSet, how to apply logicle (or other) transformations in ggcyto() (not on the original dataset)

Gating

Next we use a series of plots to guide our gating strategy for identifying the population we want to work with.

Remove outliers

We first gate on FSC.H and SSC.H to remove outliers (events that are too big or too small). The Attune instrument we use can record six decades (100-106), with the first two decades mostly occupied by electronic noise.

Let’s first define a gate and visualize it in a plot before adding it to a GatingSet.

test <- shortNames[c(3, 12*2+3, 12*5+3, 12*7+3)]
outlier.gate <- rectangleGate(filterId = "-outlier", "FSC.H" = c(1.2e5, 1e6), "SSC.H" = c(1e2, 1e6))
ggcyto(fs[test], aes(x = FSC.H, y = SSC.H), subset = "root") +
  geom_hex(bins = 64) + geom_gate(outlier.gate) + geom_stats()

Add this gate to the GatingSet

gs <- GatingSet(fs) # create a GatingSet
gs_pop_add(gs, outlier.gate, parent = "root")
[1] 2
recompute(gs)
done!

Singlet

Next let’s remove multiplets on FSC.H vs FSC.W. To do this, we could either manually set up a polygon gate, or use the automatic clustering function provided by the flowClust package. Note that in the original implementation, the flowClust() function or the tmixFilter() version that was supposed to allow for integration with the flowCore package, both were designed with different downstream actions in mind than what I want to do here (visualize with ggcyto() + geom_gate()). The openCyto package written by the same group of authors who created flowClust and ggcyto has a helper function to make this possible. See this post for a discussion on alternative ways to achieve this.

Update switch to a polygon gate as the clustering is not working well. Update 2023/10/11 visualize how the two populations differ in fluorescence

FSC.H vs FSC.W plot

ggcyto(gs[c(1, 50)], aes(x = FSC.H, y = FSC.W), subset = "-outlier") + geom_hex(bins = 128)

There is clearly a second population with the same FSC.H but higher FSC.W, indicative of doublets presence. The diffused population further to the upper right likely represent multiplets, since they have a steeper slope, indicating an increase in width without significant change in peak height

Is it necessary to gate out the doublets and multiplets? We can separate the singlets from the rest and compare their fluorescence readings

scPars <- ggcyto_par_set(limits = list(x = c(0,1e6), y = c(30,300)))
#singlet.gate <- gate_flowclust_2d(ex, "FSC.H", "FSC.W", filterId = "singlet", K = 2, quantile = 0.8)
# switch to a polygongate
polygon <- matrix(c(1e5, 1e5, 1e6, 1e6, 60, 75, 135,60), ncol = 2)
colnames(polygon) <- c("FSC.H", "FSC.W")
singlet.gate <- polygonGate(filterId = "singlet", .gate = polygon)
# use one sample for test. start by generating a filtered result
tmp <- fs[[50]]
tmp.ff <- filter(tmp, singlet.gate)
p.list <- list(
  scale_x_logicle(),
  scale_fill_discrete("Singlet", labels = c("NO", "YES")),
  theme(legend.position = "top")
)
p1 <- ggcyto(tmp, aes(x = FSC.H, y = FSC.W)) + geom_hex(bins = 80) + scPars +
  geom_gate(singlet.gate) +  geom_stats(type = c("percent", "count"), adjust = c(0.8, 0.2))
Coordinate system already present. Adding new coordinate system, which will replace the
existing one.
p2 <- ggplot(tmp, aes(x = `BL1.H`)) + geom_density(aes(fill = tmp %in% tmp.ff), alpha = 0.5) + p.list
p3 <- ggplot(tmp, aes(x = `YL2.H`)) + geom_density(aes(fill = tmp %in% tmp.ff), alpha = 0.5) + p.list
p.col <- plot_grid(p2, p3 + theme(legend.position = "none"), nrow = 2)
plot_grid(as.ggplot(p1), p.col)

Add this gate to the gatingSet

gs_pop_add(gs, singlet.gate, parent = "-outlier", name = "singlet")
[1] 3
recompute(gs)
done!
ggcyto(gs[sample(1:96, 36)], aes(x = FSC.H, y = FSC.W), subset = "-outlier") + geom_hex(bins = 128) + geom_gate("singlet") + 
  facet_wrap(~name, ncol = 6)+ scPars + 
  geom_stats(type = c("percent", "count"), location = "fixed", adjust = c(125e3, 250))
Coordinate system already present. Adding new coordinate system, which will replace the
existing one.

PHO5-mCherry induction

Plot the singlet events on GFP-RFP 2d space and check for the presence of multiple populations. If present, we will use flowClust to identify the main population and move forward.

ggcyto(gs, aes(x = BL1.H, y = YL2.H), subset = "singlet") + geom_hex(bins = 80) +
  facet_wrap(~name, ncol = 10) + scale_x_logicle() + scale_y_logicle() + panel_border()

Be careful when working with the GatingSet and GatingHierarchy objects – these are strictly reference classes, meaning that most of the operations work by pointers and the operations will change the underlying data. For example, the first line of the code below (commented out) obtains a pointer to the underlying data rather than making a copy of that data. any operations on it will change the original data as a result.

#ex <- gs_pop_get_data(gs, "singlet")[[1]]
ex <- fs[["219-555-3"]]
# set the parameters for the cluster gate
k = 1; q = 0.9
# end setting
# transform the two fluorescent parameters for clustering
lgcl <- logicleTransform("induction")
ex <- transform(ex, lgBL1.H = lgcl(`BL1.H`), lgYL2.H = lgcl(`YL2.H`))
fluo.gate <- gate_flowclust_2d(ex, "lgBL1.H", "lgYL2.H", K = k, quantile = q)
The prior specification has no effect when usePrior=no
Using the serial version of flowClust
ggcyto(ex, aes(x = lgBL1.H, y = lgYL2.H)) + geom_hex(bins = 64) + geom_gate(fluo.gate) + geom_stats()# + scale_x_logicle() + scale_y_logicle()

Even though flowClust is supposed to perform its own transformation (modified Box-Cox), empirically I found the clustering seem to work better on logicle transformed data for the two fluorescent channels. Therefore I’m transforming the underlying data of the GatingSet. Note that it seems to be difficult to “create new parameters” to store the transformed data, while keeping the original data intact. Instead, the transformation functions constructed using the constructor logicle_trans() stores the inverse transformation functions, which can be used to perform the inverse transformation when needed. Followed the manual for GatingSet here

lgcl <- logicle_trans()
transList <- transformerList(c(lgBL1.H = "BL1.H", lgclYL2.H = "YL2.H"), lgcl)
transform(gs, transList)
A GatingSet with 96 samples

to obtain the original data, use gs_pop_get_data(gs[[1]], inverse.transform = TRUE)

Now we can do the flowClust gating

dat <- gs_pop_get_data(gs, "singlet") # get parent data
inductionGate <- fsApply(dat, function(fr)
  openCyto::gate_flowclust_2d(fr, "BL1.H", "YL2.H", K = k, quantile = q)
)
gs_pop_add(gs, inductionGate, parent = "singlet", name = "induction")
recompute(gs)
ggcyto(gs, aes(x = BL1.H, y = YL2.H), subset = "singlet") + geom_hex(bins = 64) + 
  geom_gate("induction") + geom_stats(location = "data", adjust = c(0.8, 0.2), type = "count") + 
  facet_wrap(~name, ncol = 10) + panel_border()

Notice that the clustering results for 239-555-1/3 showed two populations. Here we will use a 2 cluster gating strategy to select the RFP-expressing population.

tmp <- fs[["239-555-1"]]
lgcl <- logicleTransform("induction")
tmp <- transform(tmp, lgBL1.H = lgcl(`BL1.H`), lgYL2.H = lgcl(`YL2.H`))
fluo.gate <- gate_flowclust_2d(tmp, "lgBL1.H", "lgYL2.H", K = 2, quantile = q, target = c(3,4))
ggcyto(tmp, aes(x = lgBL1.H, y = lgYL2.H)) + geom_hex(bins = 64) + geom_gate(fluo.gate) + geom_stats()# + scale_x_logicle() + scale_y_logicle()

Implement the 2 cluster gates

list.redo <- c("239-555-1", "239-555-3")
newGate <- lapply(list.redo, function(x){
  gate_flowclust_2d(dat[[x]], "BL1.H", "YL2.H", K = 2, quantile = 0.9, target = c(3,4))
})
The prior specification has no effect when usePrior=no
Using the serial version of flowClust
The prior specification has no effect when usePrior=no
Using the serial version of flowClust
names(newGate) <- list.redo
#newGate["218-555-1"] <- gate_flowclust_2d(dat[["218-555-1"]], "BL1.H", "YL2.H", K = 3, quantile = 0.9, target = c(3,4))
ggcyto(gs[list.redo], aes(x = BL1.H, y = YL2.H), subset = "singlet") + geom_hex(bins = 64) +
  geom_gate(newGate) + geom_stats()

update the inductionGate object

gs_pop_set_gate(gs[list.redo], "induction", newGate)
[[1]]
NULL

[[2]]
NULL
recompute(gs[list.redo], "induction")
done!

Check the results

ggcyto(gs, aes(x = BL1.H, y = YL2.H), subset = "singlet") + 
  geom_hex(bins = 64) + geom_gate("induction") + 
  geom_stats(type = "count", location = "data", adjust = c(0.8, 0.2)) + 
  facet_wrap(~name, ncol = 10) + panel_border()

Normalization

The amount of fluorescence per cell should scale with cell size. As a result, if the cell size distribution is significantly different across samples, there may be a problem. Is that the case?

mult_format <- function() {
     function(x) format(x/10000,digits = 2) 
}
tmp <- gs[sample(1:96, 18)]
p1 <- ggcyto(tmp[1:9], aes(x = FSC.H), subset = "induction") + 
  geom_density_ridges(aes(y = name), fill = "forestgreen", alpha = 0.8) + 
  scale_x_continuous(labels = mult_format(), name = "FSC.H x 10000") +
  facet_wrap(~NA)
p2 <- ggcyto(tmp[10:18], aes(x = FSC.H), subset = "induction") + 
  geom_density_ridges(aes(y = name), fill = "forestgreen", alpha = 0.8) + 
  scale_x_continuous(labels = mult_format(), name = "FSC.H x 10000") +
  facet_wrap(~NA)
plot_grid(as.ggplot(p1), as.ggplot(p2))
Picking joint bandwidth of 18000
Picking joint bandwidth of 18300

Is there a correlation between cell size and fluorescence?

ggcyto(tmp, aes(x = "FSC.H", y = "YL2.H"), subset = "induction") + geom_hex(bins = 64) + 
  stat_smooth(method = "loess") + axis_y_inverse_trans() +
  #scale_fill_gradientn(colours = rev(RColorBrewer::brewer.pal(11, "Spectral"))) +
  facet_wrap(~name, scales = "free") + coord_cartesian()
Coordinate system already present. Adding new coordinate system, which will replace the
existing one.

While there are certainly some differences in the cell size distribution, and fluorescence is indeed correlated with cell size, the median fluorescence intensity value may still be quite comparable. More importantly, the correction method proposed by Brian Metzger and colleagues in their 2015 paper doesn’t work well for all samples. In their work, they cared about the CV as well as the median. Removing the cell size dependence on the fluorescence measurement is thus crucial (it affects CV very strongly). For us, however, we only care about the MFI and thus I decide to skip this step. Nonetheless, I decided to calculate the normalized nGFP and nRFP, using an exponent of 1.2 that seems to work better than 1.5 for our data

Test normalization formula

mfsc <- 5e5 # based on the mode of the median of FSC.H from all samples
# fs.out is of the cytoframe class, which is a reference class. need to convert to flowframe for transformation
# https://www.bioconductor.org/packages/devel/bioc/vignettes/flowWorkspace/inst/doc/flowWorkspace-Introduction.html
exponent <- 1.2
# calculate ratio
norm.data <- fsApply(fs.out, function(cf) {
  cf <- cbind(cf, 
              nRFP = cf[,"YL2.H"] / (cf[, "FSC.H"]/mfsc)^(exponent), 
              nGFP = cf[,"BL1.H"] / (cf[, "FSC.H"]/mfsc)^(exponent))
  apply(cf[, c("FSC.H", "BL1.H", "YL2.H", "nGFP", "nRFP")], 2, median)
  }, use.exprs = TRUE) %>% 
  as_tibble(rownames = "name")  %>% 
  mutate(across(BL1.H:nRFP, ~ round(.x, 1)))

Output

The goal is to export the gated events and calculate the RFP/GFP and take the median, which will be used in downstream analyses.

Get the population stats

stats <- gs_pop_get_stats(gs) %>% 
  as_tibble() %>% 
  mutate(pop = gsub(".*/", "", pop), pop = gsub("-outlier", "cells", pop)) %>% 
  pivot_wider(names_from = pop, names_prefix = "n_", values_from = count)

Export the data

# pull all info together in a single tibble
final <- left_join(as_tibble(pData(fs)), stats, by = c("name" = "sample")) %>% 
  left_join(norm.data, by = "name")
final <- final[gtools::mixedorder(final$well),] # sort by well number, thanks to 
# https://stackoverflow.com/questions/58531533/r-sorting-all-columns-in-data-frame-by-an-alphanumeric-column
write_tsv(final, "20230208-gated-median-out.txt")
LS0tCnRpdGxlOiAiRnVsbCBzZXQgb2YgY2hpbWVyaWMgUGhvNCBpbiBQSE81cHItUkZQLCBEYXkgMSIKYXV0aG9yOiBCaW4gSGUsIExpbmRzZXkgU255ZGVyCmRhdGU6ICIyMDIyLTEwLTEwICh1cGRhdGVkIGByIGZvcm1hdChTeXMudGltZSgpLCAnJW0vJWQvJXknKWApIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiA0CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKLS0tCgpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRX0KcmVxdWlyZSh0aWR5dmVyc2UpCnJlcXVpcmUoZmxvd0NvcmUpCnJlcXVpcmUoZmxvd0NsdXN0KQpyZXF1aXJlKG9wZW5DeXRvKQpyZXF1aXJlKGdnY3l0bykKcmVxdWlyZShnZ3JpZGdlcykKcmVxdWlyZShjb3dwbG90KQpgYGAKCmBgYHtyfQpvbGQgPC0gdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgojIERhdGEKCipQbGF0ZSBkZXNpZ24qCgpTZWUgYDIwMjMwMjA4LXNhbXBsZS1saXN0LnR4dGAKCiMgQW5hbHlzaXMKCiMjIEltcG9ydCBkYXRhIGFuZCBlZGl0IHRoZSBtZXRhIGRhdGEKCmltcG9ydCB0aGUgZGF0YSBmcm9tIHRoZSBSRFNTIChqdXN0IG9uY2UpIGFuZCB0aGVuIHdyaXRlIGl0IHRvIHRoZSBsb2NhbCBkaXNrCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmRhdGEucGF0aCA9ICIvVm9sdW1lcy9yZHNzX2JoZTIvUHJvamVjdC9FMDEzLVBobzRwLWV2b2x1dGlvbi9mbG93LWN5dG9tZXRyeS9kYXRhLzIwMjMwMjI0LUxTLWNoaW1lcmEtcHJvZHVjdGlvbi1ydW4vUEhPNS1mbG93LWRhdGEvMjAyMzAyMDhwaG81LWRheTEvIgpmcyA8LSByZWFkLmZsb3dTZXQocGF0aCA9IGRhdGEucGF0aCwgcGF0dGVybiA9ICIuZmNzIiwgIyBzcGVjaWZ5IHRoZSBmaWxlIHBhdHRlcm4KICAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybWF0aW9uID0gRkFMU0UsICAjIHRoZSBvcmlnaW5hbCB2YWx1ZXMgYXJlIGFscmVhZHkgbGluZWFyaXplZC4gCiAgICAgICAgICAgICAgICAgICBlbXB0eVZhbHVlID0gRkFMU0UsICBhbHRlci5uYW1lcyA9IFRSVUUsICAgIyBjaGFuZ2UgcGFyYW1ldGVyIG5hbWVzIHRvIFIgZm9ybWF0CiAgICAgICAgICAgICAgICAgICBjb2x1bW4ucGF0dGVybiA9ICIuSHxGU0N8U1NDIikgIyBvbmx5IGxvYWQgdGhlIGhlaWdodCB2YXJpYWJsZXMgZm9yIHRoZSBmbHVvcmVzY2VudCBwYXJhbWV0ZXJzCmBgYAoKU2ltcGxpZnkgdGhlIHNhbXBsZSBuYW1lcwoKYGBge3J9CiMgaW1wb3J0IHRoZSBsb25nZXN0IHN1YnN0cmluZyBmdW5jdGlvbiBmcm9tIHRoZSBQVFhRQyBwYWNrYWdlIChodHRwczovL3JkcnIuaW8vY3Jhbi9QVFhRQy9tYW4vTENTbi5odG1sKQpzb3VyY2UoIi4uLy4uL3NjcmlwdC8yMDIyMDMyNi1zaW1wbGlmeS1uYW1lcy1zdWJyb3V0aW5lLlIiKQpvcmlOYW1lcyA8LSBzYW1wbGVOYW1lcyhmcykKc2hvcnROYW1lcyA8LSBzaW1wbGlmeU5hbWVzKG9yaU5hbWVzKSAlPiUgCiAgZ3N1YigiLmZjcyIsIiIsLikKc2FtcGxlTmFtZXMoZnMpIDwtIHNob3J0TmFtZXMKYGBgCgpNZXRhZGF0YQoKYGBge3J9CnNhbXBsZSA8LSByZWFkLmNzdigiMjAyMzAyMDgtc2FtcGxlLWxpc3QuY3N2IikgJT4lIAogIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiZmlsZSIpCnBEYXRhKGZzKSA8LSBzYW1wbGUKYGBgCgojIyBFREEKClRoZSBjb2RlIGJlbG93IGRlbW9uc3RyYXRlcyBob3cgdG8gc3Vic2V0IGEgZmxvd1NldCwgaG93IHRvIGFwcGx5IGxvZ2ljbGUgKG9yIG90aGVyKSB0cmFuc2Zvcm1hdGlvbnMgaW4gZ2djeXRvKCkgKG5vdCBvbiB0aGUgb3JpZ2luYWwgZGF0YXNldCkKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnRlc3QgPC0gc2hvcnROYW1lc1tjKDMsIDEyKjIrMywgMTIqNSszLCAxMio3KzMpXQpwMSA8LSBnZ3Bsb3QoZnNbdGVzdF0sIGFlcyh4ID0gYEJMMS5IYCkpICsgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhZXMoeSA9IG5hbWUsIGZpbGwgPSBuYW1lKSkgKyBzY2FsZV94X2xvZ2ljbGUoKQpwMiA8LSBnZ3Bsb3QoZnNbdGVzdF0sIGFlcyh4ID0gYFlMMi5IYCkpICsgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhZXMoeSA9IG5hbWUsIGZpbGwgPSBuYW1lKSkgKyBzY2FsZV94X2xvZ2ljbGUoKQp0aGVtZS5lbGVtZW50cyA8LSBsaXN0KAogIHRoZW1lX2Nvd3Bsb3QoKSwKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpCikKcCA8LSBwbG90X2dyaWQocDEgKyB0aGVtZS5lbGVtZW50cywgcDIgKyB0aGVtZS5lbGVtZW50cyArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC42LCAwLjg1KSksIG5jb2wgPSAyKQpwCmBgYAoKIyMgR2F0aW5nCgpOZXh0IHdlIHVzZSBhIHNlcmllcyBvZiBwbG90cyB0byBndWlkZSBvdXIgZ2F0aW5nIHN0cmF0ZWd5IGZvciBpZGVudGlmeWluZyB0aGUgcG9wdWxhdGlvbiB3ZSB3YW50IHRvIHdvcmsgd2l0aC4gCgojIyMgUmVtb3ZlIG91dGxpZXJzCldlIGZpcnN0IGdhdGUgb24gRlNDLkggYW5kIFNTQy5IIHRvIHJlbW92ZSBvdXRsaWVycyAoZXZlbnRzIHRoYXQgYXJlIHRvbyBiaWcgb3IgdG9vIHNtYWxsKS4gVGhlIEF0dHVuZSBpbnN0cnVtZW50IHdlIHVzZSBjYW4gcmVjb3JkIHNpeCBkZWNhZGVzICgxMF4wLTEwXjYpLCB3aXRoIHRoZSBmaXJzdCB0d28gZGVjYWRlcyBtb3N0bHkgb2NjdXBpZWQgYnkgZWxlY3Ryb25pYyBub2lzZS4KCkxldCdzIGZpcnN0IGRlZmluZSBhIGdhdGUgYW5kIHZpc3VhbGl6ZSBpdCBpbiBhIHBsb3QgYmVmb3JlIGFkZGluZyBpdCB0byBhIEdhdGluZ1NldC4KCmBgYHtyfQojdGVzdCA8LSBzaG9ydE5hbWVzW2MoMywgMTIqMiszLCAxMio1KzMsIDEyKjcrMyldCm91dGxpZXIuZ2F0ZSA8LSByZWN0YW5nbGVHYXRlKGZpbHRlcklkID0gIi1vdXRsaWVyIiwgIkZTQy5IIiA9IGMoMS4yZTUsIDFlNiksICJTU0MuSCIgPSBjKDFlMiwgMWU2KSkKZ2djeXRvKGZzW3Rlc3RdLCBhZXMoeCA9IEZTQy5ILCB5ID0gU1NDLkgpLCBzdWJzZXQgPSAicm9vdCIpICsKICBnZW9tX2hleChiaW5zID0gNjQpICsgZ2VvbV9nYXRlKG91dGxpZXIuZ2F0ZSkgKyBnZW9tX3N0YXRzKCkKYGBgCgpBZGQgdGhpcyBnYXRlIHRvIHRoZSBHYXRpbmdTZXQKCmBgYHtyfQpncyA8LSBHYXRpbmdTZXQoZnMpICMgY3JlYXRlIGEgR2F0aW5nU2V0CmdzX3BvcF9hZGQoZ3MsIG91dGxpZXIuZ2F0ZSwgcGFyZW50ID0gInJvb3QiKQpyZWNvbXB1dGUoZ3MpCmBgYAoKCiMjIyBTaW5nbGV0CgpOZXh0IGxldCdzIHJlbW92ZSBtdWx0aXBsZXRzIG9uIEZTQy5IIHZzIEZTQy5XLiBUbyBkbyB0aGlzLCB3ZSBjb3VsZCBlaXRoZXIgbWFudWFsbHkgc2V0IHVwIGEgcG9seWdvbiBnYXRlLCBvciB1c2UgdGhlIGF1dG9tYXRpYyBjbHVzdGVyaW5nIGZ1bmN0aW9uIHByb3ZpZGVkIGJ5IHRoZSBgZmxvd0NsdXN0YCBwYWNrYWdlLiBOb3RlIHRoYXQgaW4gdGhlIG9yaWdpbmFsIGltcGxlbWVudGF0aW9uLCB0aGUgYGZsb3dDbHVzdCgpYCBmdW5jdGlvbiBvciB0aGUgYHRtaXhGaWx0ZXIoKWAgdmVyc2lvbiB0aGF0IHdhcyBzdXBwb3NlZCB0byBhbGxvdyBmb3IgaW50ZWdyYXRpb24gd2l0aCB0aGUgYGZsb3dDb3JlYCBwYWNrYWdlLCBib3RoIHdlcmUgZGVzaWduZWQgd2l0aCBkaWZmZXJlbnQgZG93bnN0cmVhbSBhY3Rpb25zIGluIG1pbmQgdGhhbiB3aGF0IEkgd2FudCB0byBkbyBoZXJlICh2aXN1YWxpemUgd2l0aCBgZ2djeXRvKCkgKyBnZW9tX2dhdGUoKWApLiBUaGUgYG9wZW5DeXRvYCBwYWNrYWdlIHdyaXR0ZW4gYnkgdGhlIHNhbWUgZ3JvdXAgb2YgYXV0aG9ycyB3aG8gY3JlYXRlZCBgZmxvd0NsdXN0YCBhbmQgYGdnY3l0b2AgaGFzIGEgaGVscGVyIGZ1bmN0aW9uIHRvIG1ha2UgdGhpcyBwb3NzaWJsZS4gU2VlIFt0aGlzIHBvc3RdKGh0dHBzOi8vc3VwcG9ydC5iaW9jb25kdWN0b3Iub3JnL3AvOTY5NDUvKSBmb3IgYSBkaXNjdXNzaW9uIG9uIGFsdGVybmF0aXZlIHdheXMgdG8gYWNoaWV2ZSB0aGlzLgoKKioqVXBkYXRlKioqIHN3aXRjaCB0byBhIHBvbHlnb24gZ2F0ZSBhcyB0aGUgY2x1c3RlcmluZyBpcyBub3Qgd29ya2luZyB3ZWxsLgoqKipVcGRhdGUgMjAyMy8xMC8xMSoqKiB2aXN1YWxpemUgaG93IHRoZSB0d28gcG9wdWxhdGlvbnMgZGlmZmVyIGluIGZsdW9yZXNjZW5jZQoKKipGU0MuSCB2cyBGU0MuVyBwbG90KioKCmBgYHtyfQpnZ2N5dG8oZ3NbYygxLCA1MCldLCBhZXMoeCA9IEZTQy5ILCB5ID0gRlNDLlcpLCBzdWJzZXQgPSAiLW91dGxpZXIiKSArIGdlb21faGV4KGJpbnMgPSAxMjgpCmBgYAo+IFRoZXJlIGlzIGNsZWFybHkgYSBzZWNvbmQgcG9wdWxhdGlvbiB3aXRoIHRoZSBzYW1lIEZTQy5IIGJ1dCBoaWdoZXIgRlNDLlcsCj4gaW5kaWNhdGl2ZSBvZiBkb3VibGV0cyBwcmVzZW5jZS4gVGhlIGRpZmZ1c2VkIHBvcHVsYXRpb24gZnVydGhlciB0byB0aGUgCj4gdXBwZXIgcmlnaHQgbGlrZWx5IHJlcHJlc2VudCBtdWx0aXBsZXRzLCBzaW5jZSB0aGV5IGhhdmUgYSBzdGVlcGVyIHNsb3BlLAo+IGluZGljYXRpbmcgYW4gaW5jcmVhc2UgaW4gd2lkdGggd2l0aG91dCBzaWduaWZpY2FudCBjaGFuZ2UgaW4gcGVhayBoZWlnaHQKCklzIGl0IG5lY2Vzc2FyeSB0byBnYXRlIG91dCB0aGUgZG91YmxldHMgYW5kIG11bHRpcGxldHM/IFdlIGNhbiBzZXBhcmF0ZSB0aGUgc2luZ2xldHMgZnJvbSB0aGUgcmVzdCBhbmQgY29tcGFyZSB0aGVpciBmbHVvcmVzY2VuY2UgcmVhZGluZ3MKYGBge3J9CnNjUGFycyA8LSBnZ2N5dG9fcGFyX3NldChsaW1pdHMgPSBsaXN0KHggPSBjKDAsMWU2KSwgeSA9IGMoMzAsMzAwKSkpCiNzaW5nbGV0LmdhdGUgPC0gZ2F0ZV9mbG93Y2x1c3RfMmQoZXgsICJGU0MuSCIsICJGU0MuVyIsIGZpbHRlcklkID0gInNpbmdsZXQiLCBLID0gMiwgcXVhbnRpbGUgPSAwLjgpCiMgc3dpdGNoIHRvIGEgcG9seWdvbmdhdGUKcG9seWdvbiA8LSBtYXRyaXgoYygxZTUsIDFlNSwgMWU2LCAxZTYsIDYwLCA3NSwgMTM1LDYwKSwgbmNvbCA9IDIpCmNvbG5hbWVzKHBvbHlnb24pIDwtIGMoIkZTQy5IIiwgIkZTQy5XIikKc2luZ2xldC5nYXRlIDwtIHBvbHlnb25HYXRlKGZpbHRlcklkID0gInNpbmdsZXQiLCAuZ2F0ZSA9IHBvbHlnb24pCmBgYAoKYGBge3J9CiMgdXNlIG9uZSBzYW1wbGUgZm9yIHRlc3QuIHN0YXJ0IGJ5IGdlbmVyYXRpbmcgYSBmaWx0ZXJlZCByZXN1bHQKdG1wIDwtIGZzW1s1MF1dCnRtcC5mZiA8LSBmaWx0ZXIodG1wLCBzaW5nbGV0LmdhdGUpCnAubGlzdCA8LSBsaXN0KAogIHNjYWxlX3hfbG9naWNsZSgpLAogIHNjYWxlX2ZpbGxfZGlzY3JldGUoIlNpbmdsZXQiLCBsYWJlbHMgPSBjKCJOTyIsICJZRVMiKSksCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCikKcDEgPC0gZ2djeXRvKHRtcCwgYWVzKHggPSBGU0MuSCwgeSA9IEZTQy5XKSkgKyBnZW9tX2hleChiaW5zID0gODApICsgc2NQYXJzICsKICBnZW9tX2dhdGUoc2luZ2xldC5nYXRlKSArICBnZW9tX3N0YXRzKHR5cGUgPSBjKCJwZXJjZW50IiwgImNvdW50IiksIGFkanVzdCA9IGMoMC44LCAwLjIpKQpwMiA8LSBnZ3Bsb3QodG1wLCBhZXMoeCA9IGBCTDEuSGApKSArIGdlb21fZGVuc2l0eShhZXMoZmlsbCA9IHRtcCAlaW4lIHRtcC5mZiksIGFscGhhID0gMC41KSArIHAubGlzdApwMyA8LSBnZ3Bsb3QodG1wLCBhZXMoeCA9IGBZTDIuSGApKSArIGdlb21fZGVuc2l0eShhZXMoZmlsbCA9IHRtcCAlaW4lIHRtcC5mZiksIGFscGhhID0gMC41KSArIHAubGlzdApwLmNvbCA8LSBwbG90X2dyaWQocDIsIHAzICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwgbnJvdyA9IDIpCnBsb3RfZ3JpZChhcy5nZ3Bsb3QocDEpLCBwLmNvbCkKYGBgCgpBZGQgdGhpcyBnYXRlIHRvIHRoZSBnYXRpbmdTZXQKCmBgYHtyfQpnc19wb3BfYWRkKGdzLCBzaW5nbGV0LmdhdGUsIHBhcmVudCA9ICItb3V0bGllciIsIG5hbWUgPSAic2luZ2xldCIpCnJlY29tcHV0ZShncykKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQpnZ2N5dG8oZ3Nbc2FtcGxlKDE6OTYsIDM2KV0sIGFlcyh4ID0gRlNDLkgsIHkgPSBGU0MuVyksIHN1YnNldCA9ICItb3V0bGllciIpICsgZ2VvbV9oZXgoYmlucyA9IDEyOCkgKyBnZW9tX2dhdGUoInNpbmdsZXQiKSArIAogIGZhY2V0X3dyYXAofm5hbWUsIG5jb2wgPSA2KSsgc2NQYXJzICsgCiAgZ2VvbV9zdGF0cyh0eXBlID0gYygicGVyY2VudCIsICJjb3VudCIpLCBsb2NhdGlvbiA9ICJmaXhlZCIsIGFkanVzdCA9IGMoMTI1ZTMsIDI1MCkpCmBgYAoKIyMjIFBITzUtbUNoZXJyeSBpbmR1Y3Rpb24KClBsb3QgdGhlIHNpbmdsZXQgZXZlbnRzIG9uIEdGUC1SRlAgMmQgc3BhY2UgYW5kIGNoZWNrIGZvciB0aGUgcHJlc2VuY2Ugb2YgbXVsdGlwbGUgcG9wdWxhdGlvbnMuIElmIHByZXNlbnQsIHdlIHdpbGwgdXNlIGZsb3dDbHVzdCB0byBpZGVudGlmeSB0aGUgbWFpbiBwb3B1bGF0aW9uIGFuZCBtb3ZlIGZvcndhcmQuCgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9CmdnY3l0byhncywgYWVzKHggPSBCTDEuSCwgeSA9IFlMMi5IKSwgc3Vic2V0ID0gInNpbmdsZXQiKSArIGdlb21faGV4KGJpbnMgPSA4MCkgKwogIGZhY2V0X3dyYXAofm5hbWUsIG5jb2wgPSAxMCkgKyBzY2FsZV94X2xvZ2ljbGUoKSArIHNjYWxlX3lfbG9naWNsZSgpICsgcGFuZWxfYm9yZGVyKCkKYGBgCgo+IEJlIGNhcmVmdWwgd2hlbiB3b3JraW5nIHdpdGggdGhlIEdhdGluZ1NldCBhbmQgR2F0aW5nSGllcmFyY2h5IG9iamVjdHMgLS0gdGhlc2UgYXJlIHN0cmljdGx5IHJlZmVyZW5jZSBjbGFzc2VzLCBtZWFuaW5nIHRoYXQgbW9zdCBvZiB0aGUgb3BlcmF0aW9ucyB3b3JrIGJ5IHBvaW50ZXJzIGFuZCB0aGUgb3BlcmF0aW9ucyB3aWxsIGNoYW5nZSB0aGUgdW5kZXJseWluZyBkYXRhLiBGb3IgZXhhbXBsZSwgdGhlIGZpcnN0IGxpbmUgb2YgdGhlIGNvZGUgYmVsb3cgKGNvbW1lbnRlZCBvdXQpIG9idGFpbnMgYSBwb2ludGVyIHRvIHRoZSB1bmRlcmx5aW5nIGRhdGEgcmF0aGVyIHRoYW4gbWFraW5nIGEgY29weSBvZiB0aGF0IGRhdGEuIGFueSBvcGVyYXRpb25zIG9uIGl0IHdpbGwgY2hhbmdlIHRoZSBvcmlnaW5hbCBkYXRhIGFzIGEgcmVzdWx0LgoKYGBge3J9CiNleCA8LSBnc19wb3BfZ2V0X2RhdGEoZ3MsICJzaW5nbGV0IilbWzFdXQpleCA8LSBmc1tbIjIxOS01NTUtMyJdXQojIHNldCB0aGUgcGFyYW1ldGVycyBmb3IgdGhlIGNsdXN0ZXIgZ2F0ZQprID0gMTsgcSA9IDAuOQojIGVuZCBzZXR0aW5nCiMgdHJhbnNmb3JtIHRoZSB0d28gZmx1b3Jlc2NlbnQgcGFyYW1ldGVycyBmb3IgY2x1c3RlcmluZwpsZ2NsIDwtIGxvZ2ljbGVUcmFuc2Zvcm0oImluZHVjdGlvbiIpCmV4IDwtIHRyYW5zZm9ybShleCwgbGdCTDEuSCA9IGxnY2woYEJMMS5IYCksIGxnWUwyLkggPSBsZ2NsKGBZTDIuSGApKQpmbHVvLmdhdGUgPC0gZ2F0ZV9mbG93Y2x1c3RfMmQoZXgsICJsZ0JMMS5IIiwgImxnWUwyLkgiLCBLID0gaywgcXVhbnRpbGUgPSBxKQpnZ2N5dG8oZXgsIGFlcyh4ID0gbGdCTDEuSCwgeSA9IGxnWUwyLkgpKSArIGdlb21faGV4KGJpbnMgPSA2NCkgKyBnZW9tX2dhdGUoZmx1by5nYXRlKSArIGdlb21fc3RhdHMoKSMgKyBzY2FsZV94X2xvZ2ljbGUoKSArIHNjYWxlX3lfbG9naWNsZSgpCmBgYAoKRXZlbiB0aG91Z2ggZmxvd0NsdXN0IGlzIHN1cHBvc2VkIHRvIHBlcmZvcm0gaXRzIG93biB0cmFuc2Zvcm1hdGlvbiAobW9kaWZpZWQgQm94LUNveCksIGVtcGlyaWNhbGx5IEkgZm91bmQgdGhlIGNsdXN0ZXJpbmcgc2VlbSB0byB3b3JrIGJldHRlciBvbiBsb2dpY2xlIHRyYW5zZm9ybWVkIGRhdGEgZm9yIHRoZSB0d28gZmx1b3Jlc2NlbnQgY2hhbm5lbHMuIFRoZXJlZm9yZSBJJ20gdHJhbnNmb3JtaW5nIHRoZSB1bmRlcmx5aW5nIGRhdGEgb2YgdGhlIEdhdGluZ1NldC4gTm90ZSB0aGF0IGl0IHNlZW1zIHRvIGJlIGRpZmZpY3VsdCB0byAiY3JlYXRlIG5ldyBwYXJhbWV0ZXJzIiB0byBzdG9yZSB0aGUgdHJhbnNmb3JtZWQgZGF0YSwgd2hpbGUga2VlcGluZyB0aGUgb3JpZ2luYWwgZGF0YSBpbnRhY3QuIEluc3RlYWQsIHRoZSB0cmFuc2Zvcm1hdGlvbiBmdW5jdGlvbnMgY29uc3RydWN0ZWQgdXNpbmcgdGhlIGNvbnN0cnVjdG9yIGBsb2dpY2xlX3RyYW5zKClgIHN0b3JlcyB0aGUgaW52ZXJzZSB0cmFuc2Zvcm1hdGlvbiBmdW5jdGlvbnMsIHdoaWNoIGNhbiBiZSB1c2VkIHRvIHBlcmZvcm0gdGhlIGludmVyc2UgdHJhbnNmb3JtYXRpb24gd2hlbiBuZWVkZWQuIEZvbGxvd2VkIHRoZSBtYW51YWwgZm9yIEdhdGluZ1NldCBbaGVyZV0oaHR0cHM6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9mbG93V29ya3NwYWNlL2luc3QvZG9jL2Zsb3dXb3Jrc3BhY2UtSW50cm9kdWN0aW9uLmh0bWwjMDNfR2F0aW5nSGllcmFyY2h5KQoKYGBge3J9CmxnY2wgPC0gbG9naWNsZV90cmFucygpCnRyYW5zTGlzdCA8LSB0cmFuc2Zvcm1lckxpc3QoYyhsZ0JMMS5IID0gIkJMMS5IIiwgbGdjbFlMMi5IID0gIllMMi5IIiksIGxnY2wpCnRyYW5zZm9ybShncywgdHJhbnNMaXN0KQpgYGAKCj4gdG8gb2J0YWluIHRoZSBvcmlnaW5hbCBkYXRhLCB1c2UgYGdzX3BvcF9nZXRfZGF0YShnc1tbMV1dLCBpbnZlcnNlLnRyYW5zZm9ybSA9IFRSVUUpYAoKTm93IHdlIGNhbiBkbyB0aGUgZmxvd0NsdXN0IGdhdGluZwoKYGBge3J9CmRhdCA8LSBnc19wb3BfZ2V0X2RhdGEoZ3MsICJzaW5nbGV0IikgIyBnZXQgcGFyZW50IGRhdGEKaW5kdWN0aW9uR2F0ZSA8LSBmc0FwcGx5KGRhdCwgZnVuY3Rpb24oZnIpCiAgb3BlbkN5dG86OmdhdGVfZmxvd2NsdXN0XzJkKGZyLCAiQkwxLkgiLCAiWUwyLkgiLCBLID0gaywgcXVhbnRpbGUgPSBxKQopCmdzX3BvcF9hZGQoZ3MsIGluZHVjdGlvbkdhdGUsIHBhcmVudCA9ICJzaW5nbGV0IiwgbmFtZSA9ICJpbmR1Y3Rpb24iKQpyZWNvbXB1dGUoZ3MpCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KZ2djeXRvKGdzLCBhZXMoeCA9IEJMMS5ILCB5ID0gWUwyLkgpLCBzdWJzZXQgPSAic2luZ2xldCIpICsgZ2VvbV9oZXgoYmlucyA9IDY0KSArIAogIGdlb21fZ2F0ZSgiaW5kdWN0aW9uIikgKyBnZW9tX3N0YXRzKGxvY2F0aW9uID0gImRhdGEiLCBhZGp1c3QgPSBjKDAuOCwgMC4yKSwgdHlwZSA9ICJjb3VudCIpICsgCiAgZmFjZXRfd3JhcCh+bmFtZSwgbmNvbCA9IDEwKSArIHBhbmVsX2JvcmRlcigpCmBgYAoKTm90aWNlIHRoYXQgdGhlIGNsdXN0ZXJpbmcgcmVzdWx0cyBmb3IgMjM5LTU1NS0xLzMgc2hvd2VkIHR3byBwb3B1bGF0aW9ucy4gSGVyZSB3ZSB3aWxsIHVzZSBhIDIgY2x1c3RlciBnYXRpbmcgc3RyYXRlZ3kgdG8gc2VsZWN0IHRoZSBSRlAtZXhwcmVzc2luZyBwb3B1bGF0aW9uLgpgYGB7ciBtZXNzYWdlPUZBTFNFfQp0bXAgPC0gZnNbWyIyMzktNTU1LTEiXV0KbGdjbCA8LSBsb2dpY2xlVHJhbnNmb3JtKCJpbmR1Y3Rpb24iKQp0bXAgPC0gdHJhbnNmb3JtKHRtcCwgbGdCTDEuSCA9IGxnY2woYEJMMS5IYCksIGxnWUwyLkggPSBsZ2NsKGBZTDIuSGApKQpmbHVvLmdhdGUgPC0gZ2F0ZV9mbG93Y2x1c3RfMmQodG1wLCAibGdCTDEuSCIsICJsZ1lMMi5IIiwgSyA9IDIsIHF1YW50aWxlID0gcSwgdGFyZ2V0ID0gYygzLDQpKQpnZ2N5dG8odG1wLCBhZXMoeCA9IGxnQkwxLkgsIHkgPSBsZ1lMMi5IKSkgKyBnZW9tX2hleChiaW5zID0gNjQpICsgZ2VvbV9nYXRlKGZsdW8uZ2F0ZSkgKyBnZW9tX3N0YXRzKCkjICsgc2NhbGVfeF9sb2dpY2xlKCkgKyBzY2FsZV95X2xvZ2ljbGUoKQpgYGAKSW1wbGVtZW50IHRoZSAyIGNsdXN0ZXIgZ2F0ZXMKYGBge3J9Cmxpc3QucmVkbyA8LSBjKCIyMzktNTU1LTEiLCAiMjM5LTU1NS0zIikKbmV3R2F0ZSA8LSBsYXBwbHkobGlzdC5yZWRvLCBmdW5jdGlvbih4KXsKICBnYXRlX2Zsb3djbHVzdF8yZChkYXRbW3hdXSwgIkJMMS5IIiwgIllMMi5IIiwgSyA9IDIsIHF1YW50aWxlID0gMC45LCB0YXJnZXQgPSBjKDMsNCkpCn0pCm5hbWVzKG5ld0dhdGUpIDwtIGxpc3QucmVkbwojbmV3R2F0ZVsiMjE4LTU1NS0xIl0gPC0gZ2F0ZV9mbG93Y2x1c3RfMmQoZGF0W1siMjE4LTU1NS0xIl1dLCAiQkwxLkgiLCAiWUwyLkgiLCBLID0gMywgcXVhbnRpbGUgPSAwLjksIHRhcmdldCA9IGMoMyw0KSkKZ2djeXRvKGdzW2xpc3QucmVkb10sIGFlcyh4ID0gQkwxLkgsIHkgPSBZTDIuSCksIHN1YnNldCA9ICJzaW5nbGV0IikgKyBnZW9tX2hleChiaW5zID0gNjQpICsKICBnZW9tX2dhdGUobmV3R2F0ZSkgKyBnZW9tX3N0YXRzKCkKYGBgCgp1cGRhdGUgdGhlIGluZHVjdGlvbkdhdGUgb2JqZWN0CmBgYHtyfQpnc19wb3Bfc2V0X2dhdGUoZ3NbbGlzdC5yZWRvXSwgImluZHVjdGlvbiIsIG5ld0dhdGUpCnJlY29tcHV0ZShnc1tsaXN0LnJlZG9dLCAiaW5kdWN0aW9uIikKYGBgCgpDaGVjayB0aGUgcmVzdWx0cwpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CmdnY3l0byhncywgYWVzKHggPSBCTDEuSCwgeSA9IFlMMi5IKSwgc3Vic2V0ID0gInNpbmdsZXQiKSArIAogIGdlb21faGV4KGJpbnMgPSA2NCkgKyBnZW9tX2dhdGUoImluZHVjdGlvbiIpICsgCiAgZ2VvbV9zdGF0cyh0eXBlID0gImNvdW50IiwgbG9jYXRpb24gPSAiZGF0YSIsIGFkanVzdCA9IGMoMC44LCAwLjIpKSArIAogIGZhY2V0X3dyYXAofm5hbWUsIG5jb2wgPSAxMCkgKyBwYW5lbF9ib3JkZXIoKQpgYGAKCgojIyBOb3JtYWxpemF0aW9uCgpUaGUgYW1vdW50IG9mIGZsdW9yZXNjZW5jZSBwZXIgY2VsbCBzaG91bGQgc2NhbGUgd2l0aCBjZWxsIHNpemUuIEFzIGEgcmVzdWx0LCBpZiB0aGUgY2VsbCBzaXplIGRpc3RyaWJ1dGlvbiBpcyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBhY3Jvc3Mgc2FtcGxlcywgdGhlcmUgbWF5IGJlIGEgcHJvYmxlbS4gSXMgdGhhdCB0aGUgY2FzZT8KCmBgYHtyfQptdWx0X2Zvcm1hdCA8LSBmdW5jdGlvbigpIHsKICAgICBmdW5jdGlvbih4KSBmb3JtYXQoeC8xMDAwMCxkaWdpdHMgPSAyKSAKfQp0bXAgPC0gZ3Nbc2FtcGxlKDE6OTYsIDE4KV0KYGBgCgpgYGB7cn0KcDEgPC0gZ2djeXRvKHRtcFsxOjldLCBhZXMoeCA9IEZTQy5IKSwgc3Vic2V0ID0gImluZHVjdGlvbiIpICsgCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhZXMoeSA9IG5hbWUpLCBmaWxsID0gImZvcmVzdGdyZWVuIiwgYWxwaGEgPSAwLjgpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IG11bHRfZm9ybWF0KCksIG5hbWUgPSAiRlNDLkggeCAxMDAwMCIpICsKICBmYWNldF93cmFwKH5OQSkKcDIgPC0gZ2djeXRvKHRtcFsxMDoxOF0sIGFlcyh4ID0gRlNDLkgpLCBzdWJzZXQgPSAiaW5kdWN0aW9uIikgKyAKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyh5ID0gbmFtZSksIGZpbGwgPSAiZm9yZXN0Z3JlZW4iLCBhbHBoYSA9IDAuOCkgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gbXVsdF9mb3JtYXQoKSwgbmFtZSA9ICJGU0MuSCB4IDEwMDAwIikgKwogIGZhY2V0X3dyYXAofk5BKQpwbG90X2dyaWQoYXMuZ2dwbG90KHAxKSwgYXMuZ2dwbG90KHAyKSkKYGBgCgpJcyB0aGVyZSBhIGNvcnJlbGF0aW9uIGJldHdlZW4gY2VsbCBzaXplIGFuZCBmbHVvcmVzY2VuY2U/CmBgYHtyfQpnZ2N5dG8odG1wLCBhZXMoeCA9ICJGU0MuSCIsIHkgPSAiWUwyLkgiKSwgc3Vic2V0ID0gImluZHVjdGlvbiIpICsgZ2VvbV9oZXgoYmlucyA9IDY0KSArIAogIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIpICsgYXhpc195X2ludmVyc2VfdHJhbnMoKSArCiAgI3NjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSByZXYoUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDExLCAiU3BlY3RyYWwiKSkpICsKICBmYWNldF93cmFwKH5uYW1lLCBzY2FsZXMgPSAiZnJlZSIpICsgY29vcmRfY2FydGVzaWFuKCkKYGBgCgo+IFdoaWxlIHRoZXJlIGFyZSBjZXJ0YWlubHkgc29tZSBkaWZmZXJlbmNlcyBpbiB0aGUgY2VsbCBzaXplIGRpc3RyaWJ1dGlvbiwgYW5kIGZsdW9yZXNjZW5jZSBpcyBpbmRlZWQgY29ycmVsYXRlZCB3aXRoIGNlbGwgc2l6ZSwgdGhlIG1lZGlhbiBmbHVvcmVzY2VuY2UgaW50ZW5zaXR5IHZhbHVlIG1heSBzdGlsbCBiZSBxdWl0ZSBjb21wYXJhYmxlLiBNb3JlIGltcG9ydGFudGx5LCB0aGUgY29ycmVjdGlvbiBtZXRob2QgcHJvcG9zZWQgYnkgQnJpYW4gTWV0emdlciBhbmQgY29sbGVhZ3VlcyBpbiB0aGVpciAyMDE1IHBhcGVyIGRvZXNuJ3Qgd29yayB3ZWxsIGZvciBhbGwgc2FtcGxlcy4gSW4gdGhlaXIgd29yaywgdGhleSBjYXJlZCBhYm91dCB0aGUgQ1YgYXMgd2VsbCBhcyB0aGUgbWVkaWFuLiBSZW1vdmluZyB0aGUgY2VsbCBzaXplIGRlcGVuZGVuY2Ugb24gdGhlIGZsdW9yZXNjZW5jZSBtZWFzdXJlbWVudCBpcyB0aHVzIGNydWNpYWwgKGl0IGFmZmVjdHMgQ1YgdmVyeSBzdHJvbmdseSkuIEZvciB1cywgaG93ZXZlciwgd2Ugb25seSBjYXJlIGFib3V0IHRoZSBNRkkgYW5kIHRodXMgSSBkZWNpZGUgdG8gc2tpcCB0aGlzIHN0ZXAuIAo+IE5vbmV0aGVsZXNzLCBJIGRlY2lkZWQgdG8gY2FsY3VsYXRlIHRoZSBub3JtYWxpemVkIG5HRlAgYW5kIG5SRlAsIHVzaW5nIGFuIGV4cG9uZW50IG9mIDEuMiB0aGF0IHNlZW1zIHRvIHdvcmsgYmV0dGVyIHRoYW4gMS41IGZvciBvdXIgZGF0YQoKVGVzdCBub3JtYWxpemF0aW9uIGZvcm11bGEKCmBgYHtyfQojIGdldCBwb3B1bGF0aW9uIGRhdGEKZnMub3V0IDwtIGdzX3BvcF9nZXRfZGF0YShncywgeSA9ICJpbmR1Y3Rpb24iLCBpbnZlcnNlLnRyYW5zZm9ybSA9IFRSVUUpICMgZ2V0IHRoZSBpbnZlcnNlIHRyYW5zZm9ybWVkIGRhdGEKIyBjb21lIHVwIHdpdGggYW4gYXBwcm94aW1hdGUgRlNDLkggdmFsdWUgZm9yIGFuIGF2ZXJhZ2UgZXZlbnQgdG8gYmUgdXNlZCBhIHNjYWxhciBmb3IgdGhlIG5leHQgc3RlcApgYGAKCmBgYHtyfQptZnNjIDwtIDVlNSAjIGJhc2VkIG9uIHRoZSBtb2RlIG9mIHRoZSBtZWRpYW4gb2YgRlNDLkggZnJvbSBhbGwgc2FtcGxlcwojIGZzLm91dCBpcyBvZiB0aGUgY3l0b2ZyYW1lIGNsYXNzLCB3aGljaCBpcyBhIHJlZmVyZW5jZSBjbGFzcy4gbmVlZCB0byBjb252ZXJ0IHRvIGZsb3dmcmFtZSBmb3IgdHJhbnNmb3JtYXRpb24KIyBodHRwczovL3d3dy5iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL2Jpb2MvdmlnbmV0dGVzL2Zsb3dXb3Jrc3BhY2UvaW5zdC9kb2MvZmxvd1dvcmtzcGFjZS1JbnRyb2R1Y3Rpb24uaHRtbApleHBvbmVudCA8LSAxLjIKYGBgCgpgYGB7cn0KIyBjYWxjdWxhdGUgcmF0aW8Kbm9ybS5kYXRhIDwtIGZzQXBwbHkoZnMub3V0LCBmdW5jdGlvbihjZikgewogIGNmIDwtIGNiaW5kKGNmLCAKICAgICAgICAgICAgICBuUkZQID0gY2ZbLCJZTDIuSCJdIC8gKGNmWywgIkZTQy5IIl0vbWZzYyleKGV4cG9uZW50KSwgCiAgICAgICAgICAgICAgbkdGUCA9IGNmWywiQkwxLkgiXSAvIChjZlssICJGU0MuSCJdL21mc2MpXihleHBvbmVudCkpCiAgYXBwbHkoY2ZbLCBjKCJGU0MuSCIsICJCTDEuSCIsICJZTDIuSCIsICJuR0ZQIiwgIm5SRlAiKV0sIDIsIG1lZGlhbikKICB9LCB1c2UuZXhwcnMgPSBUUlVFKSAlPiUgCiAgYXNfdGliYmxlKHJvd25hbWVzID0gIm5hbWUiKSAgJT4lIAogIG11dGF0ZShhY3Jvc3MoQkwxLkg6blJGUCwgfiByb3VuZCgueCwgMSkpKQpgYGAKCiMgT3V0cHV0CgpUaGUgZ29hbCBpcyB0byBleHBvcnQgdGhlIGdhdGVkIGV2ZW50cyBhbmQgY2FsY3VsYXRlIHRoZSBSRlAvR0ZQIGFuZCB0YWtlIHRoZSBtZWRpYW4sIHdoaWNoIHdpbGwgYmUgdXNlZCBpbiBkb3duc3RyZWFtIGFuYWx5c2VzLgoKR2V0IHRoZSBwb3B1bGF0aW9uIHN0YXRzCgpgYGB7cn0Kc3RhdHMgPC0gZ3NfcG9wX2dldF9zdGF0cyhncykgJT4lIAogIGFzX3RpYmJsZSgpICU+JSAKICBtdXRhdGUocG9wID0gZ3N1YigiLiovIiwgIiIsIHBvcCksIHBvcCA9IGdzdWIoIi1vdXRsaWVyIiwgImNlbGxzIiwgcG9wKSkgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwb3AsIG5hbWVzX3ByZWZpeCA9ICJuXyIsIHZhbHVlc19mcm9tID0gY291bnQpCmBgYAoKRXhwb3J0IHRoZSBkYXRhCgpgYGB7cn0KIyBwdWxsIGFsbCBpbmZvIHRvZ2V0aGVyIGluIGEgc2luZ2xlIHRpYmJsZQpmaW5hbCA8LSBsZWZ0X2pvaW4oYXNfdGliYmxlKHBEYXRhKGZzKSksIHN0YXRzLCBieSA9IGMoIm5hbWUiID0gInNhbXBsZSIpKSAlPiUgCiAgbGVmdF9qb2luKG5vcm0uZGF0YSwgYnkgPSAibmFtZSIpCmZpbmFsIDwtIGZpbmFsW2d0b29sczo6bWl4ZWRvcmRlcihmaW5hbCR3ZWxsKSxdICMgc29ydCBieSB3ZWxsIG51bWJlciwgdGhhbmtzIHRvIAojIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzU4NTMxNTMzL3Itc29ydGluZy1hbGwtY29sdW1ucy1pbi1kYXRhLWZyYW1lLWJ5LWFuLWFscGhhbnVtZXJpYy1jb2x1bW4Kd3JpdGVfdHN2KGZpbmFsLCAiMjAyMzAyMDgtZ2F0ZWQtbWVkaWFuLW91dC50eHQiKQpgYGAK